﻿//document.write('<scr'+'ipt type="text/javascript" src="Kernels.js" ></scr'+'ipt>');

Filters = {};

Filters.tmpCanvas = document.createElement('canvas');
Filters.tmpCtx = Filters.tmpCanvas.getContext('2d');

if (!window.Float32Array)
	Float32Array = Array;

Filters.getPixels = function(img) 
{
	var c,ctx;
	if (img.getContext) 
	{
		c = img;
		try 
		{ 
			ctx = c.getContext('2d'); 
		} 
		catch(e) {}
	}
	if (!ctx) 
	{
		c = this.getCanvas(img.width, img.height);
		ctx = c.getContext('2d');
		ctx.drawImage(img, 0, 0);
	}
	return ctx.getImageData(0,0,c.width,c.height);
};

Filters.setPixels = function(img, imgData) 
{
	var c,ctx;
	if (img.getContext) 
	{
		c = img;
		try 
		{ 
			ctx = c.getContext('2d'); 
		} 
		catch(e) {}
	}
	if (!ctx) 
	{
		c = this.getCanvas(img.width, img.height);
		ctx = c.getContext('2d');
		ctx.drawImage(img, 0, 0);
	}
	return ctx.putImageData(imgData, 0, 0);
};

Filters.getCanvas = function(w,h) 
{
	var c = document.createElement('canvas');
	c.width = w;
	c.height = h;	
	return c;
};

Filters.filterImage = function(filter, image, var_args) 
{
	var args = [this.getPixels(image)];
	for (var i=2; i<arguments.length; i++) {
		args.push(arguments[i]);
	}
	return filter.apply(null, args);
};

Filters.countColors = function(pixels, args) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	var colorsHash = new Array();
	
	var c = 0;
	var count = 0;
	for (var i=0; i<len; i+=4) 
	{
		c = ptr[i] << 16 | ptr[i+1] << 8 | ptr[i+2];
		if (colorsHash[c] == undefined || colorsHash[c] == null || colorsHash[c] == 0)
		{
			colorsHash[c] = 1;
			count++;
		}
	}
	return count;
};


Filters.grayscale = function(pixels, args) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	for (var i=0; i<len; i+=4) 
	{
		var r = ptr[i];
		var g = ptr[i+1];
		var b = ptr[i+2];
		var v = 0.2126*r + 0.7152*g + 0.0722*b;
		ptr[i] = ptr[i+1] = ptr[i+2] = v;
	}
	return pixels;
};

Filters.toGray = function(pixels) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	
	var sw = pixels.width;
	var sh = pixels.height;
	
	var grayData = new Array(sw*sh);
	var idx = 0;
	
	for (var i=0; i<len; i+=4) 
	{
		var r = ptr[i];
		var g = ptr[i+1];
		var b = ptr[i+2];
		var v = 0.2126*r + 0.7152*g + 0.0722*b;
		grayData[idx] = v;
		idx++;		
	}
	
	return grayData;
};

Filters.toRGBA = function(grayData, w, h) 
{
	var pixels = Filters.createImageData(w, h);
	
	var ptr = pixels.data;
	var len = ptr.length;

	var idx = 0;
	var v = 0;
	
	for (i=0; i<len; i+=4) 
	{
		v = grayData[idx];
		
		ptr[i] = v;
		ptr[i+1] = v;
		ptr[i+2] = v;
		ptr[i+3] = 255;

		idx++;		
	}
	
	return pixels;
};


Filters.equalizeHistogram = function(pixels, args) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	var numPixel = len / 4;	

	var histR = new Array(256);
	var histG = new Array(256);
	var histB = new Array(256);
	
	for (i = 0; i < 256; i++ )
    {
    	histR[i] = histG[i] = histB[i] = 0;
    }
	
	var r, g, b;
	
	for (var i=0; i<len; i+=4) 
	{
		histR[ptr[i]]++;
		histG[ptr[i+1]]++;
		histB[ptr[i+2]]++;
	}
	
	var eHistR = Filters.equalize(histR, numPixel);
	var eHistG = Filters.equalize(histG, numPixel);
	var eHistB = Filters.equalize(histB, numPixel);
	
	for (var i=0; i<len; i+=4) 
	{
		ptr[i] = eHistR[ptr[i]];
		ptr[i+1] = eHistG[ptr[i+1]];
		ptr[i+2] = eHistB[ptr[i+2]];
	}
	
	return pixels;
};

Filters.equalize = function(histogram, numPixel) 
{
	var equalizedHistogram = new Array(256);
    var coef = 255.0 / numPixel;

    var cdf = histogram[0] * coef;		//Cumulative Distribution Function
    equalizedHistogram[0] = cdf;

    for (i = 1; i < 256; i++ )
    {
        cdf += histogram[i] * coef;
        equalizedHistogram[i] = cdf;
    }

    return equalizedHistogram;
};


Filters.noise = function(pixels, pdf_name, arg1, arg2)
{
	var sw = pixels.width;
	var sh = pixels.height;
	
	var grayData = Filters.toGray(pixels);
	var len = grayData.length;
	
	switch(pdf_name.toLowerCase()) 
	{
	case 'gaussian':
		initGaussian(arg1, arg2);
	    for (idx=0;idx<len;idx++)
			grayData[idx] += randDouble(255, seed_Gaussian());
		break;
	case 'rayleigh':
		initRayleigh(arg1, arg2);
	    for (idx=0;idx<len;idx++)
			grayData[idx] += randDouble(255, seed_Rayleigh());
		break;
	case 'erlang':
		initErlang(arg1, arg2);
	    for (idx=0;idx<len;idx++)
			grayData[idx] += randDouble(255, seed_Erlang(arg1, arg2));
		break;
	case 'uniform':
		initUniform(arg1, arg2);
	    for (idx=0;idx<len;idx++)
			grayData[idx] += randDouble(255, seed_Gaussian());
		break;
	case 'impulse':
	    var noise = 0;
		for (idx=0;idx<len;idx++)
		{
			noise = Math.random();
			if (noise < arg1)
				grayData[idx] = 0;
			else if (noise > arg2)
				grayData[idx] = 255;
		}
		break;
	}
	
	return Filters.toRGBA(grayData, sw, sh);
};


Filters.invert = function(pixels, args) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	for (var i=0; i<len; i+=4) 
	{
		ptr[i] = 255 - ptr[i];
		ptr[i+1] = 255 - ptr[i+1];
		ptr[i+2] = 255 - ptr[i+2];
	}
	return pixels;
};

Filters.brightness = function(pixels, adjustment) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	for (var i=0; i<len; i+=4)  
	{
		ptr[i] += adjustment;
		ptr[i+1] += adjustment;
		ptr[i+2] += adjustment;
	}
	return pixels;
};

Filters.threshold = function(pixels, threshold) 
{
	var ptr = pixels.data;
	var len = ptr.length;
	for (var i=0; i<len; i+=4)  
	{
		var r = ptr[i];
		var g = ptr[i+1];
		var b = ptr[i+2];
		var v = (0.2126*r + 0.7152*g + 0.0722*b >= threshold) ? 255 : 0;
		ptr[i] = ptr[i+1] = ptr[i+2] = v
	}
	return pixels;
};

Filters.median = function(pixels, kernelSide)
{
	var side = kernelSide;
	var halfSide = Math.floor(side/2);
	var src = pixels.data;
	var sw = pixels.width;
	var sh = pixels.height;

	var w = sw;
	var h = sh;
	var output = Filters.createImageData(w, h);
	var dst = output.data;

	var len = side * side;
	var medianR = new Array(len);
	var medianG = new Array(len);
	var medianB = new Array(len);

	for (var y=0; y<h; y++) 
	{
		for (var x=0; x<w; x++) 
		{
			var sy = y;
			var sx = x;
			var dstOff = (y*w+x)*4;
			var r=0, g=0, b=0, a=0, idx=0;
			for (var cy=0; cy<side; cy++) 
			{
				for (var cx=0; cx<side; cx++) 
				{
					var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide));
					var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide));
					var srcOff = (scy*sw+scx)*4;

					medianR[idx] = src[srcOff];
					medianG[idx] = src[srcOff+1];
					medianB[idx] = src[srcOff+2];
					idx++;
				}
			}
			
			medianR.sort();
			medianG.sort();
			medianB.sort();
			idx = Math.floor(len/2);
			dst[dstOff] = medianR[idx];
			dst[dstOff+1] = medianG[idx];
			dst[dstOff+2] = medianB[idx];
			dst[dstOff+3] = 255;
		}
	}
	return output;
}

Filters.createImageData = function(w,h) 
{
	return this.tmpCtx.createImageData(w,h);
};

Filters.convoluteKernel = function(pixels, kernel) 
{
	var side = kernel.length;
	var halfSide = Math.floor(side/2);

	var src = pixels.data;
	var sw = pixels.width;
	var sh = pixels.height;

	var w = sw;
	var h = sh;
	var output = Filters.createImageData(w, h);
	var dst = output.data;


	for (var y=0; y<h; y++) 
	{
		for (var x=0; x<w; x++) 
		{
			var sy = y;
			var sx = x;
			var dstOff = (y*w+x)*4;
			var r=0, g=0, b=0, a=0;
			for (var cy=0; cy<side; cy++) 
			{
				for (var cx=0; cx<side; cx++) 
				{
					var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide));
					var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide));
					var srcOff = (scy*sw+scx)*4;
					var wt = kernel[cy][cx];
					r += src[srcOff] * wt;
					g += src[srcOff+1] * wt;
					b += src[srcOff+2] * wt;
				}
			}
			dst[dstOff] = r;
			dst[dstOff+1] = g;
			dst[dstOff+2] = b;
			dst[dstOff+3] = 255;
		}
	}
	return output;
};

Filters.blur = function(pixels, kernel_sigma, kernel_size)
{	
	var k = new GaussianKernel();
	k.setSigma(kernel_sigma);
	var kernel = k.kernel2D(kernel_size);
	
	return Filters.convoluteKernel(pixels, kernel);	
};

Filters.loG = function(pixels, kernel_sigma, kernel_size)
{	
	var k = new LoGKernel();
	k.setSigma(kernel_sigma);
	var kernel = k.kernel2D(kernel_size);
	
//	if (!k.kernelValid(kernel, kernel_size, kernel_sigma))
//	{
//		var t = 0.09 * kernel_size;
//		alert("Condition failed! \n 0.5 < sigma < (0.09 x size = " + t.toString() + ")");
//	}
	
	return Filters.convoluteKernel(pixels, kernel);	
};

Filters.canny = function(pixels, lowThreshold, highThreshold)
{
	var imageWidth = pixels.width;
	var imageHeight = pixels.height;
	
	var dataLen = imageWidth * imageHeight;

    var tempImg = new Array(dataLen);
    var binImg = new Array(dataLen);

    // processing start and stop X,Y positions    
    var rectLeft = 0;
    var rectTop = 0;
    var rectWidth = imageWidth;
    var rectHeight = imageHeight;
    
    var startX = rectLeft + 1;
    var startY = rectTop + 1;
    var stopX = startX + rectWidth - 2;
    var stopY = startY + rectHeight - 2;

    var width = rectWidth - 2;
    var height = rectHeight - 2;

    var dstStride = imageWidth;
    var srcStride = imageWidth;

    var dstOffset = dstStride - rectWidth + 2;
    var srcOffset = srcStride - rectWidth + 2;

    // pixel's value and gradients
    var gx, gy;
    //
    var orientation, toAngle = 180.0 / Math.PI;
    var leftPixel = 0, rightPixel = 0;

    // STEP 1 - blur image
    // Please blur by yourself before calling edge detection

    // orientation array
    var orients = new Array(width * height);
    // gradients array
    
    var gradients = new Array(imageWidth);
	for (i = 0; i < imageWidth; i++) 
		gradients[i] = new Array(imageHeight);

    var maxGradient = -100000;

    // do the job
    var srcImg = Filters.toGray(pixels);
    
    // allign pointer
    var src = srcStride * startY + startX;

    // STEP 2 - calculate magnitude and edge orientation
    var p = 0;

    // for each line
    for (y = startY; y < stopY; y++)
    {
        // for each pixel
        for (x = startX; x < stopX; x++, src++, p++)
        {
            gx = srcImg[src - srcStride + 1] + srcImg[src + srcStride + 1]
               - srcImg[src - srcStride - 1] - srcImg[src + srcStride - 1]
               + 2 * (srcImg[src + 1] - srcImg[src - 1]);

            gy = srcImg[src - srcStride - 1] + srcImg[src - srcStride + 1]
                   - srcImg[src + srcStride - 1] - srcImg[src + srcStride + 1]
                   + 2 * (srcImg[src - srcStride] - srcImg[src + srcStride]);

            // get gradient value
            gradients[x][y] = Math.sqrt(gx * gx + gy * gy);
            if (gradients[x][y] > maxGradient)
                maxGradient = gradients[x][y];

            // --- get orientation
            if (gx == 0)
            {
                // can not divide by zero
                orientation = (gy == 0) ? 0 : 90;
            }
            else
            {
                var div = gy / gx;

                // handle angles of the 2nd and 4th quads
                if (div < 0)
                {
                    orientation = 180 - Math.atan(-div) * toAngle;
                }
                // handle angles of the 1st and 3rd quads
                else
                {
                    orientation = Math.atan(div) * toAngle;
                }

                // get closest angle from 0, 45, 90, 135 set
                if (orientation < 22.5)
                    orientation = 0;
                else if (orientation < 67.5)
                    orientation = 45;
                else if (orientation < 112.5)
                    orientation = 90;
                else if (orientation < 157.5)
                    orientation = 135;
                else orientation = 0;
            }

            // save orientation
            orients[p] = Math.floor(orientation);
        }
        src += srcOffset;
    }

    // STEP 3 - suppres non maximums
    var dst = 0;
    // allign pointer
    dst += dstStride * startY + startX;

    p = 0;

    // for each line
    for (y = startY; y < stopY; y++)
    {
        // for each pixel
        for (x = startX; x < stopX; x++, dst++, p++)
        {
            // get two adjacent pixels
            switch (orients[p])
            {
                case 0:
                    leftPixel = gradients[x - 1][y];
                    rightPixel = gradients[x + 1][y];
                    break;
                case 45:
                    leftPixel = gradients[x - 1][y + 1];
                    rightPixel = gradients[x + 1][y - 1];
                    break;
                case 90:
                    leftPixel = gradients[x][y + 1];
                    rightPixel = gradients[x][y - 1];
                    break;
                case 135:
                    leftPixel = gradients[x + 1][y + 1];
                    rightPixel = gradients[x - 1][y - 1];
                    break;
            }
            // compare current pixels value with adjacent pixels
            if ((gradients[x][y] < leftPixel) || (gradients[x][y] < rightPixel))
            {
                tempImg[dst] = 0;
            }
            else
            {
                tempImg[dst] = Math.floor(gradients[x][y] / maxGradient * 255);
            }
        }
        dst += dstOffset;
    }

    // STEP 4 - hysteresis
    var dst = 0;
    var bin = 0;

    // allign pointer
    dst += dstStride * startY + startX;
    bin += dstStride * startY + startX;

    // for each line
    for (y = startY; y < stopY; y++)
    {
        // for each pixel
        for (x = startX; x < stopX; x++, dst++, bin++)
        {
            if (tempImg[dst] < highThreshold)
            {
                if (tempImg[dst] < lowThreshold)
                {
                    // non edge
                    tempImg[dst] = 0;
                }
                else
                {
                    // check 8 neighboring pixels
                    if ((tempImg[dst-1] < highThreshold) &&
                        (tempImg[dst+1] < highThreshold) &&
                        (tempImg[dst-dstStride - 1] < highThreshold) &&
                        (tempImg[dst-dstStride] < highThreshold) &&
                        (tempImg[dst-dstStride + 1] < highThreshold) &&
                        (tempImg[dst+dstStride - 1] < highThreshold) &&
                        (tempImg[dst+dstStride] < highThreshold) &&
                        (tempImg[dst+dstStride + 1] < highThreshold))
                    {
                        tempImg[dst] = 0;
                    }
                    else
                    {
                        binImg[bin] = 255;
                    }
                }
            }
            else
            {
                binImg[bin] = 255;
            }
        }
        dst += dstOffset;
        bin += dstOffset;
    }

    return Filters.toRGBA(binImg, imageWidth, imageHeight);
};


